#!/usr/bin/env python
# encoding: utf-8


"""
@file: bijiaodaxiao.py
@time: 2016/11/21 上午9:30
"""

from mathsolver.functions.root.jiefangchenzu import *
from mathsolver.functions.budengshi import common_opers as co
from sympy import S, expand, simplify, factor, degree, sqrt
from itertools import chain
from mathsolver.functions.budengshi.ineqs_funcs import Compare
from mathsolver.functions.mathematica.mathematicaSolve import MathematicaSolve


# print list(combinations(['a', 'b', 'c', 'd'], 2))
# print reduce(lambda a, b: a * b, [2, ])


def square_simp(f):
    """
    讲多项式表示成平方的形式
    :param f:
    :return:
    >>> square_simp('a**2 - 2*a + 3 - (2 - a)')
    (a - 1/2)**2 + 3/4
    >>> square_simp('a**2 + b**2 - (2*a + 4*b - 5)')
    (a - 1)**2 + (b - 2)**2
    >>> square_simp('x**2 + 2*y**2 - ((x+y)*y)')
    3*y**2/4 + (x - y/2)**2
    """

    def symb_poly(e, x_symbol='x'):
        e = expand(e)
        coefs_dict = co.coefficients_dict(e)
        simp_poly = sympify('0')
        for m, m_coef in coefs_dict.items():
            if len(m.free_symbols) == 1 and str(list(m.free_symbols)[0]) == str(x_symbol):
                simp_poly += m * m_coef
        return simp_poly

    f = expand(f)
    symbs = list(f.free_symbols)
    # syms_combins = co.flatten([map(lambda t: reduce(lambda a, b: a * b, t), combinations(symbs, i)) for i in
    #                           range(1, len(symbs) + 1)])
    d = degree(f)
    if d == 2:  # 二次项
        if len(symbs) == 1:  # 只含有一个未知数
            return co.top_point_expr(f, x_symbol=symbs[0])[0]
        elif len(symbs) == 2:  # 含有两个未知数
            s1, s2 = symbs
            if f.coeff(s1 ** 2) < 0 or f.coeff(s2 ** 2) < 0:
                pm = -1
                f = -f
            else:
                pm = 1
            if not f.coeff(s1 * s2):  # 不含有 s1*s2的项
                s1_poly = symb_poly(f, s1)
                s1_poly = co.top_point_expr(s1_poly, x_symbol=s1)[0]  # 含有未知数1的定点表达式
                s2_poly = symb_poly(f, s2)
                s2_poly = co.top_point_expr(s2_poly, x_symbol=s2)[0]  # 含有未知数2的定点表达式
                sur_poly = simplify(f - expand(s1_poly) - expand(s2_poly))
                simp_f = s1_poly + s2_poly + sur_poly
                return pm * simp_f
            else:
                # a*x**2 + b*x*y + c*y**2 + d = (sqrt(a) + c'*y)**2 + c''*y**2
                # 则 2*sqrt(a)*c' = b => c' = b/(2*sqrt(a)); c'' = c - c'**2
                s1_s2_coef = f.coeff(s1 * s2)
                s1_quad_coef = f.coeff(s1 ** 2)
                s2_quad_coef = f.coeff(s2 ** 2)
                x_symb, y_symb = (s1, s2) if s1_quad_coef >= s2_quad_coef else (s2, s1)  # 选择一个较大的符号作为x
                a = f.coeff(x_symb ** 2)
                b = s1_s2_coef
                c = f.coeff(y_symb ** 2)
                c1 = b / (2 * sqrt(a))
                c2 = c - c1 ** 2
                const = co.split_mons_const(f)[1]
                poly = (sqrt(a) * x_symb + c1 * y_symb) ** 2 + c2 * y_symb ** 2 + const
                return pm * poly
        else:
            return factor(f)
    else:  # 多次项
        return factor(f)


# 1.利用平方大于等于零来比较大小
# 比较x^{2}+3与3x的大小.
# 比较a^{2}+b^{2}与2(2a-b)-5的大小. 含有两个未知数的
class BiJiaoDaXiao001(BaseFunction):
    def solver(self, *args):
        if len(args) != 2:
            raise Exception('Type Match Error')
        self.label.add('比较大小-做差法')
        poly1 = args[0].sympify()
        poly2 = args[1].sympify()
        poly = square_simp(poly1 - poly2)
        symbs = list(poly.free_symbols)
        if len(symbs) == 1:  # 如果只含有一个未知数
            ineq_set = solveset(poly > 0, domain=S.Reals)
            eq_set = solveset(poly, domain=S.Reals)
            if ineq_set:
                ineq_op = '>'
            else:
                ineq_op = '<'
            if eq_set:
                eq_op = '='
            else:
                eq_op = ''
            comp_op = ineq_op + eq_op
        else:  # 如果含有两个未知数
            coefs_dict = co.coefficients_dict(poly)
            for m, c in coefs_dict.items():
                if isinstance(m, Pow):
                    if c > 0:
                        comp_op = '>'
                    else:
                        comp_op = '<'
                    break
        self.steps.append(
            ['\because (%s) - (%s)' % (new_latex(poly1), new_latex(poly2)), ' = ' + new_latex(poly) + comp_op + '0'])
        self.steps.append(['\therefore %s %s %s' % (new_latex(poly1), comp_op, new_latex(poly2)), ''])
        self.output.append(BaseIneq([poly1, comp_op, poly2]))
        return self


# 设a=(\\frac{1}{3})^{-0.2},b=log_{3}2,c=log_{9}5,则a,b,c的大小关系是()
class BiJiaoDaXiao002(BaseFunction):
    def solver(self, *args):
        self.label.add('比较大小-做差法')
        if len(args) == 1 and (isinstance(args[0], BaseVariables) or
                               (isinstance(args[0], BaseMultiple) and args[0].type == 'Variables')):
            symbs = tuple(map(sympify, args[0].value))
            values = list(map(lambda symb: list(self.search(symb))[0], symbs))
            eqs = list(zip(symbs, values))
        elif len(args) == 3:
            eqs = []
            for sv in args[:2]:
                if isinstance(sv, BaseFuncEq):
                    eqs.append(sv.value)
                elif isinstance(sv, BaseEq):
                    eqs.append(sv.value)
                elif isinstance(sv, BaseEqs):
                    eqs.extend(sv.sympify())
        else:
            eqs = args[0].sympify()
        symbs = list(map(lambda symbv: str(symbv[0]), eqs))
        comp_vars = args[-1].value
        if len(symbs) != len(comp_vars):
            raise 'try fail'
        self.steps.append(['由题可知 ', ', '.join(symbs) + ' 的关系为:'])
        sorted_symbs = list(map(lambda symbv: str(symbv[0]), sorted(eqs, key=lambda svt: sympify(svt[1]))))
        sorted_print = '<'.join(sorted_symbs)
        self.steps.append([sorted_print, ''])
        self.label.add('比较大小')
        self.output.append(BaseVariables(sorted_symbs))
        return self


class BiJiaoDaXiao003(BaseFunction):
    def solver(self, *args):
        self.label.add('比较大小-做差法')
        return self


# 将比较大小的问题 转变为解不等式的问题
# style1 Input: paramer1:不等式或不等式组(条件);  paramer2:(目标)不等式或不等式组 Output: 目标是否恒成立
# style2 Input: paramer1:不等式或不等式组(目标) 从Known中搜寻变量的取值范围
class BiJiaoDaXiaoWithInEq(BaseFunction):
    @staticmethod
    def _extract_set(m_s):
        """
        提取mathematica解集的结果, 将其转换为 字典结构
        :param m_s: 
        :return: 
        """
        symbs_intls = {}
        for _sk, _intls_list in m_s.items():
            if _intls_list == EmptySet():
                return EmptySet()
            for _intls in _intls_list:
                _set = zip(_sk, _intls)
                symbs_intls.update(dict(_set))
        last_intls = {}
        last_intls.update(symbs_intls)
        for symb, intl in symbs_intls.items():
            _l = intl.left
            _r = intl.right
            if last_intls.get(_l, None):
                _l_intl = last_intls[_l]
                _l_intl_l = _l_intl.left
                _l_intl_r = _l_intl.right
                if _l_intl_r == S.Infinity or _l_intl_r.is_real:
                    n_intl = Interval(_l_intl_l, symb, left_open=_l_intl.left_open, right_open=intl.left_open)
                    last_intls[_l] = n_intl
            if last_intls.get(_r, None):
                _r_intl = last_intls[_r]
                _r_intl_l = _r_intl.left
                _r_intl_r = _r_intl.right
                if _r_intl_l == -S.Infinity or _r_intl_l.is_real:
                    n_intl = Interval(symb, _r_intl_r, left_open=intl.left_open, right_open=_r_intl.right_open)
                    last_intls[_r] = n_intl
        return last_intls

    def solver(self, *args):
        self.label.add('比较大小-解不等式法')
        flag = True
        if len(args) == 2:
            ineqs1 = []
            arg1 = args[0]
            arg2 = args[1]
            if isinstance(arg1, BaseIneq):
                ineqs1.append(arg1.sympify())
            elif isinstance(arg1, BaseIneqs):
                ineqs1.extend(arg1.sympify())
            else:
                raise Exception('Paramer first must be ineq or ineqs')
            ineqs2 = []
            if isinstance(arg2, BaseIneq):
                ineqs2.append(arg2.sympify())
            elif isinstance(arg2, BaseIneqs):
                ineqs2.extend(arg2.sympify())
            else:
                raise Exception('Paramer second must be ineq or ineqs')
            set1 = MathematicaSolve().solver(BaseIneqs(ineqs1)).output[0].value  # 只有条件的时候解集
            set1_m = BiJiaoDaXiaoWithInEq._extract_set(set1)
            set2 = MathematicaSolve().solver(BaseIneqs(list(chain(ineqs1, ineqs2)))).output[0].value  # 加入目标不等式的解集
            set2_m = BiJiaoDaXiaoWithInEq._extract_set(set2)
        elif len(args) == 1:
            arg2 = args[0]
            ineqs1 = []
            ineqs2 = []
            if isinstance(arg2, BaseIneq):
                ineqs2.append(arg2.sympify())
            elif isinstance(arg2, BaseIneqs):
                ineqs2.extend(arg2.sympify())
            symbs = list(map(sympify, co.ineqs_symbs(ineqs2)))
            set1_m = {}
            for s in symbs:
                s_intl = self.search(s)
                if self.search(s):
                    set1_m[s] = s_intl
                    ineqs1.extend(co.interval_2_ineq_list(s_intl, s))
            ineqs2 = list(chain(ineqs1, ineqs2))
            set2 = MathematicaSolve().solver(BaseIneqs(list(chain(ineqs1, ineqs2)))).output[0].value  # 加入目标不等式的解集
            set2_m = BiJiaoDaXiaoWithInEq._extract_set(set2)
        else:
            raise Exception('Illegal parameters')
        if set2_m == EmptySet():
            flag = False
        # 如果set1 的解集是 set2的 子集，那么目标不等式则恒成立。
        else:
            for symb, intl in set1_m.items():
                intl2 = set2_m.get(symb, None)
                if not intl2:
                    flag = False
                    break
                if not intl.is_subset(intl2):
                    flag = False
                    break
        if flag:
            self.steps.append([arg2.printing(), '恒成立'])
        else:
            self.steps.append([arg2.printing(), '不成立'])
        self.output.append(BaseVariable(flag))
        return self


# 比较大小
# input: poly1,poly2,poly3.....
# output: poly1 < poly2 < poly3 的 ineq
class BiJiaoDaXiao(BaseFunction):
    CLS = [BiJiaoDaXiaoWithInEq, BiJiaoDaXiao001, BiJiaoDaXiao002, Compare]

    def solver(self, *args):
        r = None
        for cl in BiJiaoDaXiao.CLS:
            try:
                obj = cl(verbose=True)
                obj.known = self.known
                r = obj.solver(*args)
                break
            except Exception:
                pass
        if not r:
            raise Exception('Can not compare the ineq')
        return r


if __name__ == '__main__':
    # print BiJiaoDaXiao(verbose=True).solver(BasePoly('x**2 + 2*x'), BasePoly('-x - 3')).steps
    # print BiJiaoDaXiao(verbose=True).solver(BasePoly('x**2 + 2*y**2'), BasePoly('(x+y)*y')).output[0].sympify()
    # _solve = BiJiaoDaXiao().solver(BaseIneqs([['0', '<', 'a', '<', 'b', '<', '1']]),
    #                                BaseIneq(['log(b-a)', '<', '0']))
    _solve = BiJiaoDaXiao(known={sympify('a'): Interval.open(0, 1), sympify('b'): Interval.open(0, 1)}).solver(
        BaseIneq(['a*b + 1', '>', 'a + b'])
    )
